知识点名称:bind方式启动服务

编号: K6-4

前驱知识点编号:K6-1,K3-2

作者:刘凤华

讲义内容:

前面我们提到如果想让服务与调用者进行方法调用和数据交互时,应该使用bindService的方式启动服务,先来看bindService启动的服务的主要特点:

(1). bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里所提到的client指的是组件,比如某个Activity。
(2).客户端client(即调用bindService的一方,比如某个Activity)可以通过IBinder接口获取Service的实例,从而可以实现在client端直接调用Service中的方法以实现灵活的交互,并且可借助IBinder实现跨进程的client-server的交互,这在纯startService启动的Service中是无法实现的。
(3).不同于startService启动的服务默认无限期执行(可以通过Context的stopService或Service的stopSelf方法停止运行),bindService启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。当没有任何client与Service绑定的时候,Service会自行销毁(通过startService启动的除外)。
(4). startService和bindService二者执行的回调方法不同:startService启动的服务会涉及Service的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind等回调方法。

使用bindService主要分两种情形:

  1. Service的调用者与Service在同一个App中;
  2. Service的调用者是App1中的一个Activity,而Service是App2中的Service,调用者与service分属两个App,这种情形下主要用于实现跨进程的通信。

下面先来看第一种情形,即Service的调用者Activity与Service在同一个App中,该情形也是我们在实际开发中用到最多的情形,因此,第二种情形不再赘述。

下面我们通过一个例子演示一下第一种情形下bindService的基本使用流程。

首先我们有一个MyService,该类继承自Service,在其主要的生命周期回调方法中都加入输出语句。MyService代码如下:

public class MyService extends Service {

public class MyBinder extends Binder{

public MyService getService(){

return MyService.this;

}

}

//通过binder实现调用者Activity与Service之间的通信

private MyBinder binder = new MyBinder();

private final Random generator = new Random();

public void onCreate() {

Log.i("DemoLog","MyService->onCreate");

super.onCreate();

}

public int onStartCommand(Intent intent, int flags, int startId) {

Log.i("DemoLog", "MyService -> onStartCommand, startId: " + startId + ", Thread: " + Thread.currentThread().getName());

return START_NOT_STICKY;

}

public IBinder onBind(Intent intent) {

Log.i("DemoLog", "MyService -> onBind ");

return binder;

}

public boolean onUnbind(Intent intent) {

Log.i("DemoLog", "MyService -> onUnbind");

return false;

}

public void onDestroy() {

Log.i("DemoLog", "MyService -> onDestroy");

super.onDestroy();

}

//getRandomNumber是Service暴露出去供Activity调用的公共方法

public int getRandomNumber(){

return generator.nextInt();

}

}

我们先分析一下上面的代码,调用者Activity要想和Service进行交互,那么Service中需要做什么呢?使用bindService将Activity与Service联系在一起的关键是binder,在MyService中,写了一个内部类MyBinder,该类有个公共方法getService,通过该方法可以获取包含MyBinder的MyService。如果想要自己的Service支持bindService启动方式,就必须在Service的onBind中返回一个IBinder类型的实例。因此实例化了一个MyBinder的实例binder作为MyService的字段,并且将其作为onBind的返回值。也就是说,Service需要完成一下两件主要的事情:
(1).在Service的onBind方法中返回IBinder类型的实例。
(2). onBind方法返回的IBinder的实例需要能够返回Service实例本身或者通过binder暴露出Service公共方法。通常情况下,最简单明了的做法就是将binder弄成Service的内部类,然后在binder中加入类似于getService之类的方法返回包含binder的Service,这样Activity可以通过该方法得到Service实例。

Service创建好以后,我们来创建一个它的调用者Activity,命名为ActivityA,是App的启动界面,代码如下:

public class ActivityA extends Activity implements Button.OnClickListener {

private TestService service = null;

private boolean isBound = false;

private ServiceConnection conn = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder binder) {

isBound = true;

TestService.MyBinder myBinder = (TestService.MyBinder)binder;

service = myBinder.getService();

Log.i("DemoLog", "ActivityA onServiceConnected");

int num = service.getRandomNumber();

Log.i("DemoLog", "ActivityA中调用MyService的getRandomNumber方法" );

}

public void onServiceDisconnected(ComponentName name) {

isBound = false;

Log.i("DemoLog", "ActivityA onServiceDisconnected");

}

};

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_a);

Log.i("DemoLog", "ActivityA -> onCreate”);

}

public void onClick(View v) {

if(v.getId() == R.id.btnBindService){

//单击了“bindService”按钮

Intent intent = new Intent(this, TestService.class);

intent.putExtra("from", "ActivityA");

Log.i("DemoLog", "-----------------------------");

Log.i("DemoLog", "ActivityA执行bindService");

bindService(intent, conn, BIND_AUTO_CREATE);

}else if(v.getId() == R.id.btnUnbindService){

//单击了“unbindService”按钮

if(isBound){

Log.i("DemoLog", "-----------------------------");

Log.i("DemoLog", "ActivityA执行unbindService");

unbindService(conn);

}

}else if(v.getId() == R.id.btnStartActivityB){

//单击了“start ActivityB”按钮

Intent intent = new Intent(this, ActivityB.class);

Log.i("DemoLog", "-----------------------------");

Log.i("DemoLog", "ActivityA启动ActivityB");

startActivity(intent);

}else if(v.getId() == R.id.btnFinish){

//单击了“Finish”按钮

Log.i("DemoLog", "-----------------------------");

Log.i("DemoLog", "ActivityA执行finish");

this.finish();

}

}

protected void onDestroy() {

super.onDestroy();

Log.i("DemoLog", "ActivityA -> onDestroy");

}

}

通过上面的代码,我们分析一下调用者需要做的工作。代码在调用者ActivityA中初始化了一个ServiceConnection类型的实例,需要重写其onServiceConnected方法以及onServiceDisconnected方法。需要将这个ServiceConnection类型的实例作为参数传给bindService方法,当Service还没有创建的时候,Android会先创建Service的实例,然后执行Service的onBind方法,得到IBinder类型的实例,将该方法作为参数传入ActivityA的ServiceConnection的onServiceConnected方法中,onServiceConnected方法的执行表明ActivityA可以获取到Service的IBinder类型的实例,然后将IBinder转换为自己实际的Binder类型,然后可以通过其直接获取Service的实例或者通过Binder直接执行公共方法,这取决于Service中Binder的具体实现。在本例中,在onServiceConnected方法中,调用者ActivityA通过binder的getService方法获取到了与其对应的Service,然后就可以直接调用Service的公共方法以达到使用Service的目的,这样ActivityA与Service之间就通过IBinder建立了连接,从而进行交互。当ActivityA与Service失去连接时会触发onServiceDisconnected方法。

从上面的分析可以看出,ActivityA需要做到以下几点:
(1).创建ServiceConnection类型的实例,并重写其onServiceConnected方法和onServiceDisconnected方法。
(2).当Android执行onServiceConnected回调方法时,可以通过IBinder实例得到Service的实例对象或直接调用binder的公共方法,这样就实现了ActivityA与Service的连接。
(3).当Android执行onServiceDisconnected回调方法时,表示ActivityA与Service之间断开了连接,因此需要写一些断开连接后需要做的处理。

在知道了如何让ActivityA与Service进行交互之后,来运行这个App,观察各个回调方法的执行过程,另外注意在ActivityA的界面上主要有“bindService”按钮和“unbindService”按钮。

点击ActivityA中的“bindService”按钮,然后点击”unbindService”按钮,输出结果如下所示:

ActivityA -> onCreate


ActivityA执行bindService

MyService->onCreate

MyService -> onBind

ActivityA onServiceConnected

ActivityA中调用MyService的getRandomNumber方法


ActivityA执行unbindService

MyService -> onUnbind

MyService -> onDestroy

通过上面的运行结果可以看到,在调用了bindService之后,由于Service此时还不存在,那么Android就会首先创建一个TestService的实例,并执行其onCreate回调方法,onCreate方法在其生命周期中只会被调用一次。然后会调用Service的onBind方法,该方法只有在第一次bindService调用后才会执行,onBind执行后会返回一个IBinder类型的实例,此时Android会将该IBinder实例存起来,这个IBinder实例是对所有Activity共享的。当下次其他的Activity执行bindService的时候,不会再执行onBind方法,因为之前已经得到了一个IBinder实例,Android会直接使用这个IBinder实例。在得到了IBinder实例之后,Android会执行Activity的ServiceConnection中的onServiceConnected方法,在该方法中会得到IBinder实例,并通过该IBinder实例得到了MyService实例,这样ActivityA就通过IBinder与MyService建立了连接,就可以调用MyService的公共方法,比如调用其getRandomNumber方法获得随机数等。

以上介绍了如何通过绑定的方式启动一个服务。

results matching ""

    No results matching ""